package dviz.visualSystem.impl;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.TreeSet;

import com.jme.scene.Node;
import com.jme.scene.state.ZBufferState;
import com.jme.scene.state.ZBufferState.TestFunction;
import com.jme.system.DisplaySystem;
import com.jme.util.Timer;

import dviz.DebugSwitch;
import dviz.visualAspect.AnnotationHelper;
import dviz.visualAspect.MetaPropertyMap;
import dviz.visualAspect.MetaPropertyMap.Type;
import dviz.visualSystem.AnimationSystem;

/*
 * AnimationSystemImpl is the main interface class for the render system to 
 * underlying model. It's main function contains creating and managing animation object, 
 * updating the property of animation object at every loop, invoke corresponding function 
 * on animation object upon request. 
 * 
 * Every invoke function will be queued until render system explicitly call update function,
 *  those function than will be executed one after another in order. This is to prevent changing
 *  of data during render time which will result in unpredictable outcome.
 */
/**
 * @author zxq071000
 */
public class AnimationSystemImpl implements AnimationSystem {

	/**
	 * @uml.property  name="propertyQuery"
	 */
	TreeSet<PropertyQueryable> propertyQuery;

	/**
	 * @return
	 * @uml.property name="systemRootNode"
	 */
	public Node getSystemRootNode() {
		return systemRootNode;
	}

	/**
	 * @param propertyQuery
	 * @uml.property name="propertyQuery"
	 */
	public void setPropertyQuery(TreeSet<PropertyQueryable> propertyQuery) {
		this.propertyQuery = propertyQuery;
	}

	/**
	 * @uml.property  name="aniModel"
	 * @uml.associationEnd  
	 */
	AniModelMap aniModelMap;

	/**
	 * @uml.property  name="systemRootNode"
	 */
	Node systemRootNode;
	/**
	 * @uml.property  name="backGroundNode"
	 */
	HashSet<Node> backGroundNode;

	/**
	 * @uml.property  name="animationClassLocator"
	 * @uml.associationEnd  
	 */
	AnimationClassLocator animationClassLocator;

	LinkedList<AnimationSystemCall> queuedInvokation;

	static class AnimationSystemCall {
		public Object key;
		public String clazz;
		public HashMap<String, Object> argument;
		public HashMap<String, MetaPropertyMap> propertyQueriable;
		public boolean attachToRoot;
		public String actionName;
		public int type;
		public static final int TYPE_INVOKATION = 0;
		public static final int TYPE_REMOVE = 1;
		public static final int TYPE_CREATEOBJECT = 2;

		public StackTraceElement[] DEBUG_DATA;

		public AnimationSystemCall(boolean DEBUG) {
			if (DEBUG) {
				DEBUG_DATA = DebugSwitch.getStackTrace(3);
			}
		}
	}

	private boolean isPrimitive(Object o) {
		if (o instanceof Integer || o instanceof Float || o instanceof Boolean
				|| o instanceof Double || o instanceof Long
				|| o instanceof Byte || o instanceof Character
				|| o instanceof Short || o instanceof String)
			return true;
		return false;
	}

	// Replace all non-primitive type object with AnimationObject object found
	// with that object as key in the system
	public HashMap<String, Object> transformArgumentList(
			HashMap<String, Object> in) {

		HashMap<String, Object> out = new HashMap<String, Object>();
		for (String key : in.keySet()) {
			Object ino = in.get(key);
			if (ino != null)
				out.put(key, transformArgument(ino));
		}
		return out;
	}

	private Object transformArgument(Object ino) {
		if (ino == null)
			return null;
		if (isPrimitive(ino)) {
			return ino;
		} else {
			Object ano = aniModelMap.getAnimationObject(ino);
			// If it is a background object, it is not handled as usual
			// hierachy.
			if (ano instanceof BackgroundObject)
				return null;
			if (ano == null)
				throw new RuntimeException(
						"Object is not mapped to any animation object yet:"
								+ ino + "<->" + ino);
			return ano;
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see dviz.visualSystem.AnimationSystem#invokeAction(java.lang.Object,
	 * java.lang.String, java.util.HashMap)
	 */
	public void invokeAction(Object key, String actionName,
			HashMap<String, Object> argument) {
		synchronized (queuedInvokation) {
			if (key == null)
				System.currentTimeMillis();
			AnimationSystemCall animationSystemCall = new AnimationSystemCall(
					DebugSwitch.AnimationSystemImpl_DEBUG);
			animationSystemCall.key = key;
			animationSystemCall.actionName = actionName;
			animationSystemCall.argument = argument;
			queuedInvokation.addFirst(animationSystemCall);
		}
	}

	private static int UID = 0;

	@SuppressWarnings("unchecked")
	private final void createObjectCall(Object key, String clazz,
			HashMap<String, Object> argument,
			// Collection<String> propertyQueriable, boolean attachToRoot) {
			HashMap<String, MetaPropertyMap> propertyQueriable,
			boolean attachToRoot) throws Exception {
		// Find class with short name clazz, if two class is having the same
		// short name, exception will be thrown
		Class clz = animationClassLocator.findClass(clazz);
		// Constructor for animationObject with one parameter of type
		// <HashMap>
		Constructor constructor = clz
				.getConstructor(new Class[] { HashMap.class });
		argument.put("nodeName", clazz + "@" + (UID++));
		HashMap<String, Object> tArg = transformArgumentList(argument);
		AnimationObject animationObject = (AnimationObject) constructor
				.newInstance(new Object[] { tArg });

		// Check if there is any property mapping is not mapped
		// for (String s : propertyQueriable) {
		for (String s : propertyQueriable.keySet()) {
			if (!AnnotationHelper.existsSetter(clz, s)) {
				// WARNING:
				System.out
						.println("AnimationSystemImpl->CreateObjectCall() Cannot find setter function on mapped VO property: "
								+ s + "@" + clazz);
			}
		}

		// If the object created is a BackGround Object, configure it
		// correspondingly
		animationObject.getMappedProperty().putAll(propertyQueriable);
		if (animationObject instanceof BackgroundObject) {
			ZBufferState stat = DisplaySystem.getDisplaySystem().getRenderer()
					.createZBufferState();
			stat.setWritable(false);
			stat.setFunction(TestFunction.Always);

			Node renderNode = animationObject.getRenderNode();
			renderNode.setRenderState(stat);
			renderNode.updateRenderState();
			backGroundNode.add(renderNode);
		} else {
			if (attachToRoot)
				systemRootNode.attachChild(animationObject.getRenderNode());
		}
		aniModelMap.put(key, animationObject);

		// TODO: if creation of an animation object failed , create a dummy
		// animation object and put onto screen
		System.out.println("AnimationSystemImpl->CreateObject()" + clazz);
	}

	private final void removeObjectCall(Object key) {
		AnimationObject ano = aniModelMap.getAnimationObject(key);
		ano.getRenderNode().removeFromParent();
		System.out.println("AnimationSystemImpl->removeObjectCall()" + key);
	}

	private final void invokeActionCall(Object key, String actionName,
			HashMap<String, Object> argument) throws Exception {
		synchronized (queuedInvokation) {
			// try {
			AnimationObject ano = aniModelMap.getAnimationObject(key);
			Method invokeMethod = AnnotationHelper.findInvokeFunction(ano,
					actionName);
			if (invokeMethod == null)
				throw new RuntimeException("Cannot find invoke action: "
						+ actionName);
			HashMap<String, Object> targ = transformArgumentList(argument);
			invokeMethod.invoke(ano, new Object[] { targ });
			// System.out.println("AnimationSystemImpl->InvokeAction()"
			// + actionName);
			// } catch (Exception e) {
			// System.err.println("AnimationSystemImpl->InvokeAction() Error "
			// + key + "@" + actionName);
			// e.printStackTrace();
			// }
		}
		System.out.println("AnimationSystemImpl->invokeActionCall() "
				+ actionName + "@" + key);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see dviz.visualSystem.AnimationSystem#createObject(java.lang.Object,
	 * java.util.HashMap, java.util.Collection)
	 */
	public void createObject(Object key, String clazz,
			HashMap<String, Object> argument,
			HashMap<String, MetaPropertyMap> propertyQueriable,
			boolean attachToRoot) {
		synchronized (queuedInvokation) {
			// Add stack trace of the target code
			AnimationSystemCall animationSystemCall = new AnimationSystemCall(
					DebugSwitch.AnimationSystemImpl_DEBUG);
			animationSystemCall.type = AnimationSystemCall.TYPE_CREATEOBJECT;
			animationSystemCall.key = key;
			animationSystemCall.clazz = clazz;
			animationSystemCall.argument = argument;
			animationSystemCall.propertyQueriable = propertyQueriable;
			animationSystemCall.attachToRoot = attachToRoot;
			queuedInvokation.addFirst(animationSystemCall);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see dviz.visualSystem.AnimationSystem#removeObject(java.lang.Object)
	 */
	public void removeObject(Object key) {
		synchronized (queuedInvokation) {
			AnimationSystemCall animationSystemCall = new AnimationSystemCall(
					DebugSwitch.AnimationSystemImpl_DEBUG);
			animationSystemCall.type = AnimationSystemCall.TYPE_REMOVE;
			animationSystemCall.key = key;
			queuedInvokation.addFirst(animationSystemCall);
		}
	}

	Timer timer;
	float fpt;

	// This method will
	// 1) update all property that is mapped in the animation
	// object by querying the under lying model that it is mapped to.
	// 2) Execute queued invokation invoke,remove,create
	public void update() {
		// execute queued invokation
		if (timer == null) {
			timer = Timer.getTimer();
			fpt = 0;
		} else {
			fpt = timer.getTimePerFrame();
//			if (fpt > 0.05f)
//				fpt = 0.05f;
			// System.out.println(fpt);
		}
		synchronized (queuedInvokation) {
			while (queuedInvokation.size() != 0) {
				AnimationSystemCall animationSystemCall = queuedInvokation
						.removeLast();
				try {
					switch (animationSystemCall.type) {
					case AnimationSystemCall.TYPE_CREATEOBJECT:
						createObjectCall(animationSystemCall.key,
								animationSystemCall.clazz,
								animationSystemCall.argument,
								animationSystemCall.propertyQueriable,
								animationSystemCall.attachToRoot);
						break;
					case AnimationSystemCall.TYPE_INVOKATION:
						invokeActionCall(animationSystemCall.key,
								animationSystemCall.actionName,
								animationSystemCall.argument);
						break;
					case AnimationSystemCall.TYPE_REMOVE:
						removeObjectCall(animationSystemCall.key);
						break;
					}
				} catch (Exception e) {
					System.err
							.println("AnimationSystemImpl->update() Exception: "
									+ animationSystemCall.type);
					e.printStackTrace();
					if (animationSystemCall.DEBUG_DATA != null) {
						System.err
								.println("Corresponding Target Code Stack Trace:");
						for (int i = 0; i < animationSystemCall.DEBUG_DATA.length; i++) {
							System.err.println("\tat "
									+ animationSystemCall.DEBUG_DATA[i]);
						}
					}
					System.exit(0);
				}
			}
		}
		// update the property
		for (AnimationObject ano : aniModelMap.getAnimationObjects()) {
			for (String animProp : ano.getMappedProperty().keySet()) {
				MetaPropertyMap property = ano.getMappedProperty()
						.get(animProp);
				try {
				
					if (property.type == Type.ONCHANGE) {
						Object value = queryProperty(ano, property);
						if (value != null) {
							ano.setProperty(animProp, value);
							ano.updateProperty(animProp);
						}
					} else if (property.type == Type.INTERPOLATE) {
						Object value = queryProperty(ano, property);
						if (value != null) {
							ano.setProperty(animProp, value);
						}
						ano.updateProperty(animProp);
					}
				} catch (Exception e) {
					System.err
							.println("AnimationSystemImpl->update() Error Update Property->"
									+ animProp);
					e.printStackTrace();
					System.exit(0);
				}
			}
		}

		// Pass the update event to the AnimationObjects
		for (AnimationObject animationObject : aniModelMap
				.getAnimationObjects()) {
			Node renderNode = animationObject.getRenderNode();
//			animationObject.renderUpdate(fpt);
			animationObject.update(fpt);
			renderNode.updateGeometricState(fpt, true);
			renderNode.updateModelBound();
			renderNode.updateRenderState();
		}
		systemRootNode.updateRenderState();
	}

	protected Object queryProperty(AnimationObject animationObject,
			MetaPropertyMap property) {
		Object key = aniModelMap.getModelObject(animationObject);
		try {
			for (PropertyQueryable query : propertyQuery) {
				Object ino = null;

				ino = query.queryProperty(key, property);

				// if (ino == null)
				// continue;
				// Object outo = transformArgument(ino);
				// return outo;
				return ino;
			}
		} catch (Exception e) {
			System.out.println("AnimationSystemImpl->queryProperty() ");
			e.printStackTrace();
			throw new RuntimeException("Exception while querying property");
		}

		throw new RuntimeException("Cannot find property " + property + "@"
				+ animationObject);
	}

	/**
	 * @return
	 * @uml.property name="aniModel"
	 */
	public AniModelMap getAniModelMap() {
		return aniModelMap;
	}

	/**
	 * @return
	 * @uml.property name="backGroundNode"
	 */
	public HashSet<Node> getBackGroundNode() {
		return backGroundNode;
	}

	private AnimationSystemImpl() {
		propertyQuery = new TreeSet<PropertyQueryable>(
				new Comparator<PropertyQueryable>() {

					@Override
					public int compare(PropertyQueryable o1,
							PropertyQueryable o2) {
						return o2.getPriority() - o1.getPriority();
					}
				});
		aniModelMap = new AniModelMap();
		systemRootNode = new Node("Animation System Root Node");
		backGroundNode = new HashSet<Node>();
		// Test purpose, delete after
		// Sphere box = new Sphere("", 500, 500, 30);
		// systemRootNode.attachChild(box);

		// systemRootNode.attachChild(new Sphere("", 20, 20, 10));

		animationClassLocator = new AnimationClassLocator();
		queuedInvokation = new LinkedList<AnimationSystemCall>();
	}

	/**
	 * @return
	 * @uml.property name="animationClassLocator"
	 */
	public AnimationClassLocator getAnimationClassLocator() {
		return animationClassLocator;
	}

	/**
	 * @return
	 * @uml.property name="propertyQuery"
	 */
	public TreeSet<PropertyQueryable> getPropertyQuery() {
		return propertyQuery;
	}

	public static AnimationSystemImpl createAnimationSystem() {
		return new AnimationSystemImpl();
	}
}
